#include "VideoProcessor.h"
#include "core/gamestream/GameStreamManager.h"
#include "core/gamestream/XComponentBackend.h"
#include "core/utils/AVUtils.h"
#include <multimedia/player_framework/native_avcodec_videodecoder.h>
#include <cstdint>
using namespace std::string_literals;

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN  0x0
#define LOG_TAG     "LimelightVideoCallbacks"
#include <hilog/log.h>


using LimelightVideoCallbacks = VideoProcessor::LimelightVideoCallbacks;

int LimelightVideoCallbacks::setup(int videoFormat, int width, int height, int redrawRate, void *context, int drFlags) {
    OH_LOG_INFO(LOG_APP,
        "Limelight setting-up decoder renderer (or video renderer) with video format %{public}d, "
        "geometry %{public}d x %{public}d @ %{public}d, drFlags %{public}d, context %{public}d",
        videoFormat, width, height, redrawRate, drFlags, reinterpret_cast<uintptr_t>(context)
    );
    INIT_VIDEO_PROCESSOR_SELF_OR_ELSE(return -1);
    self.limelightParams = {
        .videoFormat = videoFormat,
        .width = width,
        .height = height,
        .redrawRate = redrawRate,
        .context = context,
        .drFlags = drFlags
    };
    return 0;
}

void LimelightVideoCallbacks::start() {
    OH_LOG_INFO(LOG_APP, "Limelight instructed decoder renderer start");
    INIT_VIDEO_PROCESSOR_SELF_OR_ELSE(return);
    /* setup and start video decoder */
    const std::string mimeType = [&] {
        if (self.limelightParams.videoFormat & VIDEO_FORMAT_MASK_H264) {
            return OH_AVCODEC_MIMETYPE_VIDEO_AVC;
        }
        if (self.limelightParams.videoFormat & VIDEO_FORMAT_MASK_H265) {
            return OH_AVCODEC_MIMETYPE_VIDEO_HEVC;
        }
        OH_LOG_WARN(LOG_APP, "Unsupported video format %{public}d", self.limelightParams.videoFormat);
        return "unsupported";
    }();
    GameStreamManager::videoStats.setStreamGeometry(GameStreamManager::streamConfig.width, GameStreamManager::streamConfig.height);
    GameStreamManager::videoStats.setDecoder(mimeType);
    self.decoder = OH_VideoDecoder_CreateByMime(mimeType.c_str());
    if (self.decoder == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Failed to create video decoder of mime %{public}s", mimeType.c_str());
        GameStreamManager::emitEvent("GenericError", {"Failed to create video decoder of mime " + mimeType + "."});
        return;
    }
    const auto cleanup1 = [&] {
        auto *dec = self.decoder;
        self.decoder = nullptr;
        AV_CALL_OR_ELSE(OH_VideoDecoder_Destroy(dec),);
    };
    AV_CALL_OR_ELSE(OH_VideoDecoder_RegisterCallback(self.decoder, VideoDecoderCallbacks::callbacks, &self), cleanup1(); return);
    const auto format = AVUtils::makeAVFormat([&] () -> OH_AVFormat* {
        OH_AVFormat *fmt = OH_AVFormat_CreateVideoFormat(
            mimeType.c_str(),
            self.limelightParams.width,
            self.limelightParams.height
        );
        if (fmt == nullptr) {
            return nullptr;
        }
        if (!OH_AVFormat_SetDoubleValue(fmt, OH_MD_KEY_FRAME_RATE, self.limelightParams.redrawRate)) {
            OH_AVFormat_Destroy(fmt);
            return nullptr;
        }
        if (!OH_AVFormat_SetIntValue(fmt, OH_MD_KEY_VIDEO_ENABLE_LOW_LATENCY, 1)) {
            OH_AVFormat_Destroy(fmt);
            return nullptr;
        }
        return fmt;
    });
    if (!format) {
        OH_LOG_FATAL(LOG_APP, "Failed to construct video decoder format");
        cleanup1();
        return;
    }
    AV_CALL_OR_ELSE(OH_VideoDecoder_Configure(self.decoder, format.get()), cleanup1(); return);
    AV_CALL_OR_ELSE(OH_VideoDecoder_SetSurface(self.decoder, self.nativeWindow), cleanup1(); return);
    AV_CALL_OR_ELSE(OH_VideoDecoder_Prepare(self.decoder), cleanup1(); return);
    AV_CALL_OR_ELSE(OH_VideoDecoder_Start(self.decoder), cleanup1(); return);
}

void LimelightVideoCallbacks::stop() {
    OH_LOG_INFO(LOG_APP, "Limelight instructed decoder renderer stop");
    INIT_VIDEO_PROCESSOR_SELF_OR_ELSE(return);
    self.xcomp.stopFlag = true;
}

void LimelightVideoCallbacks::cleanup() {
    OH_LOG_INFO(LOG_APP, "Limelight instructed decoder renderer cleanup");
    INIT_VIDEO_PROCESSOR_SELF_OR_ELSE(return);
    self.xcomp.stopFlag = true;
}

int LimelightVideoCallbacks::submitDecodeUnit(PDECODE_UNIT decodeUnit) {
    if constexpr (DEBUG) {
        OH_LOG_DEBUG(LOG_APP, "Limelight submitting a decode unit, frame no %{public}d type %{public}d length %{public}d",
            decodeUnit->frameNumber, decodeUnit->frameType, decodeUnit->fullLength);
    }
    INIT_VIDEO_PROCESSOR_SELF_OR_ELSE(return DR_NEED_IDR);
    if (self.xcomp.stopFlag) {
        OH_LOG_INFO(LOG_APP, "Calling submitDecodeUnit on already stopped XComponentBackend");
        return DR_NEED_IDR;
    }
    int bufferNumber = 0;
    for (struct {
                decltype(decodeUnit->bufferList) entry;
                int64_t offsetInEntry;
                uint8_t *getAddr() const {
                    return reinterpret_cast<uint8_t*>(entry->data) + offsetInEntry;
                }
                int64_t getLength() const {
                    return entry->length - offsetInEntry;
                }
                void advance(int64_t length) {
                    offsetInEntry += length;
                    if (offsetInEntry >= entry->length) {
                        entry = entry->next;
                        offsetInEntry = 0;
                    }
                }
                operator bool() const {
                    return entry;
                }
            } decodeUnitIter = { .entry = decodeUnit->bufferList, .offsetInEntry = 0 }; decodeUnitIter; ) {
        AVBufferPair buffer;
        {
            std::unique_lock l(self.decodeMtx);
            self.decodeReadyCond.wait(l, [&] { return !self.decodeQueue.empty() || self.xcomp.stopFlag; });
            if (self.xcomp.stopFlag) {
                return DR_NEED_IDR;
            }
            buffer = self.decodeQueue.front();
            self.decodeQueue.pop_front();
        }
        AVUtils::PtsAsBufferCtx bufferCtx = [&] {
            OH_AVCodecBufferAttr attr;
            AV_CALL_OR_ELSE(OH_AVBuffer_GetBufferAttr(buffer.buffer, &attr), return AVUtils::PtsAsBufferCtx());
            return *reinterpret_cast<AVUtils::PtsAsBufferCtx*>(&attr.pts);
        }();
        bufferCtx.belongToFrame = decodeUnit->frameNumber;
        const auto onFailure = [&] {
            buffer.healthy = false;
            AV_CALL_OR_ELSE(OH_VideoDecoder_PushInputBuffer(self.decoder, buffer.index),);
            return DR_NEED_IDR;
        };
        /* copy payload one whole buffer at a time */
        bufferNumber++;
        const int32_t capacity = OH_AVBuffer_GetCapacity(buffer.buffer);
        if (capacity < 0) {
            OH_LOG_WARN(LOG_APP, "Failed to get buffer %{public}d capacity", buffer.index);
            return onFailure();
        }
        if constexpr (DEBUG) {
            OH_LOG_DEBUG(LOG_APP, "Buffer %{public}d is of size %{public}d", buffer.index, capacity);
        }
        uint8_t *const addr = OH_AVBuffer_GetAddr(buffer.buffer);
        if (addr == nullptr) {
            OH_LOG_WARN(LOG_APP, "Failed to get buffer %{public}d address", buffer.index);
            return onFailure();
        }
        int64_t bufferFilled = 0;
        for (; bufferFilled < capacity && decodeUnitIter; ) {
            /* fill this buffer until the buffer is full or decode unit is exhausted */
            const int64_t toCopy = std::min<int64_t>({decodeUnitIter.getLength(), capacity - bufferFilled});
            std::memcpy(addr + bufferFilled, decodeUnitIter.getAddr(), toCopy);
            decodeUnitIter.advance(toCopy);
            bufferFilled += toCopy;
        }
        /* set buffer attrs */
        OH_AVCodecBufferAttr attr = {
            .pts = *reinterpret_cast<decltype(attr.pts)*>(&bufferCtx),
            .size = static_cast<int32_t>(bufferFilled),
            .offset = 0,
            .flags = static_cast<uint32_t>(AVCODEC_BUFFER_FLAGS_NONE
                | (decodeUnitIter ? AVCODEC_BUFFER_FLAGS_INCOMPLETE_FRAME : AVCODEC_BUFFER_FLAGS_NONE)
                // NOTE see Limelight.h::FRAME_TYPE_IDR
                | (decodeUnit->frameType == FRAME_TYPE_IDR ? AVCODEC_BUFFER_FLAGS_INCOMPLETE_FRAME : AVCODEC_BUFFER_FLAGS_NONE)
            ),
        };
        AV_CALL_OR_ELSE(OH_AVBuffer_SetBufferAttr(buffer.buffer, &attr), return onFailure());
        /* submit buffer to decoder */
        AV_CALL_OR_ELSE(OH_VideoDecoder_PushInputBuffer(self.decoder, buffer.index), return DR_NEED_IDR);
        GameStreamManager::videoStats.reportSubmitted(
            bufferCtx.uuid,
            decodeUnit->fullLength,
            decodeUnit->frameHostProcessingLatency,
            decodeUnit->enqueueTimeMs,
            decodeUnit->receiveTimeMs
        );
        if constexpr (DEBUG) {
            OH_LOG_DEBUG(LOG_APP, "Submitted %{public}d buffers for frame no %{public}d", bufferNumber, decodeUnit->frameNumber);
        }
    }
    if constexpr (DEBUG) {
        OH_LOG_DEBUG(LOG_APP, "Limelight submitted a decode unit, frame no %{public}d type %{public}d length %{public}d, buffers %{public}d",
            decodeUnit->frameNumber, decodeUnit->frameType, decodeUnit->fullLength, bufferNumber);
    }
    return DR_OK;
}

